package com.engine.salary.service.impl;

import cn.hutool.core.util.NumberUtil;
import com.alibaba.fastjson.JSONObject;
import com.api.formmode.mybatis.util.SqlProxyHandle;
import com.engine.common.util.ServiceUtil;
import com.engine.core.impl.Service;
import com.engine.hrmelog.entity.dto.LoggerContext;
import com.engine.salary.biz.OtherDeductionBiz;
import com.engine.salary.config.SalaryElogConfig;
import com.engine.salary.constant.SalaryDefaultTenantConstant;
import com.engine.salary.encrypt.EncryptUtil;
import com.engine.salary.entity.datacollection.AddUpDeduction;
import com.engine.salary.entity.datacollection.DataCollectionEmployee;
import com.engine.salary.entity.datacollection.dto.OtherDeductionListDTO;
import com.engine.salary.entity.datacollection.dto.OtherDeductionRecordDTO;
import com.engine.salary.entity.datacollection.param.*;
import com.engine.salary.entity.datacollection.po.OtherDeductionPO;
import com.engine.salary.entity.salaryacct.po.SalaryAcctEmployeePO;
import com.engine.salary.entity.taxagent.dto.TaxAgentManageRangeEmployeeDTO;
import com.engine.salary.entity.taxagent.param.TaxAgentQueryParam;
import com.engine.salary.entity.taxagent.po.TaxAgentPO;
import com.engine.salary.enums.OperateTypeEnum;
import com.engine.salary.enums.UserStatusEnum;
import com.engine.salary.enums.auth.AuthFilterTypeEnum;
import com.engine.salary.exception.SalaryRunTimeException;
import com.engine.salary.mapper.datacollection.OtherDeductionMapper;
import com.engine.salary.mapper.sys.SalarySysConfMapper;
import com.engine.salary.service.AddUpDeductionService;
import com.engine.salary.service.OtherDeductionService;
import com.engine.salary.service.SalaryEmployeeService;
import com.engine.salary.service.TaxAgentService;
import com.engine.salary.service.auth.AuthService;
import com.engine.salary.service.auth.AuthServiceImpl;
import com.engine.salary.sys.entity.po.SalarySysConfPO;
import com.engine.salary.sys.entity.vo.OrderRuleVO;
import com.engine.salary.sys.service.SalarySysConfService;
import com.engine.salary.sys.service.impl.SalarySysConfServiceImpl;
import com.engine.salary.util.SalaryDateUtil;
import com.engine.salary.util.SalaryEntityUtil;
import com.engine.salary.util.SalaryI18nUtil;
import com.engine.salary.util.db.MapperProxyFactory;
import com.engine.salary.util.excel.ExcelComment;
import com.engine.salary.util.excel.ExcelParseHelper;
import com.engine.salary.util.excel.ExcelSupport;
import com.engine.salary.util.excel.ExcelUtil;
import com.engine.salary.util.page.PageInfo;
import com.engine.salary.util.page.SalaryPageUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import weaver.file.ImageFileManager;
import weaver.general.Util;
import weaver.hrm.User;

import java.io.InputStream;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.util.*;
import java.util.stream.Collectors;

import static com.engine.salary.constant.SalaryDefaultTenantConstant.DEFAULT_TENANT_KEY;
import static com.engine.salary.util.excel.ExcelSupport.EXCEL_TYPE_XLSX;

public class OtherDeductionServiceImpl extends Service implements OtherDeductionService {
    private EncryptUtil encryptUtil = new EncryptUtil();

    private OtherDeductionMapper getOtherDeductionMapper() {
        return MapperProxyFactory.getProxy(OtherDeductionMapper.class);
    }

    private OtherDeductionBiz getOtherDeductionBiz() {
        return new OtherDeductionBiz();
    }

    private TaxAgentService getTaxAgentService(User user) {
        return ServiceUtil.getService(TaxAgentServiceImpl.class, user);
    }

    private AddUpDeductionService getAddUpDeductionService(User user) {
        return ServiceUtil.getService(AddUpDeductionServiceImpl.class, user);
    }

    private SalaryEmployeeService getSalaryEmployeeService(User user) {
        return ServiceUtil.getService(SalaryEmployeeServiceImpl.class, user);
    }

    private SalarySysConfMapper getSalarySysConfMapper() {
        return SqlProxyHandle.getProxy(SalarySysConfMapper.class);
    }

    private SalarySysConfService getSalarySysConfService(User user) {
        return ServiceUtil.getService(SalarySysConfServiceImpl.class, user);
    }

    public AuthService getAuthService(User user) {
        return ServiceUtil.getService(AuthServiceImpl.class, user);
    }

    @Override
    public OtherDeductionPO getById(Long id) {
        return getOtherDeductionMapper().getById(id);
    }


    @Override
    public PageInfo<OtherDeductionListDTO> listPage(OtherDeductionQueryParam queryParam) {
        //申报月份
        List<String> declareMonth = queryParam.getDeclareMonth();
        if (CollectionUtils.isNotEmpty(declareMonth)) {
            queryParam.setDeclareMonth(declareMonth.stream().filter(StringUtils::isNotBlank).map(e -> e + "-01 00:00:00").collect(Collectors.toList()));
            queryParam.setDeclareMonthDate(declareMonth.stream().filter(StringUtils::isNotBlank).map(e -> e + "-01 00:00:00").map(SalaryDateUtil::dateStrToLocalTime).collect(Collectors.toList()));
        }

        //排序配置
        OrderRuleVO orderRule = getSalarySysConfService(user).orderRule();
        queryParam.setOrderRule(orderRule);

        queryParam.setFilterType(AuthFilterTypeEnum.DATA_OPT);
        List<OtherDeductionListDTO> list = listAuth(queryParam);

        PageInfo<OtherDeductionListDTO> page = SalaryPageUtil.buildPage(queryParam.getCurrent(), queryParam.getPageSize(),
                list, OtherDeductionListDTO.class);
        encryptUtil.decryptList(page.getList(), OtherDeductionListDTO.class);
        SalaryI18nUtil.i18nList(page.getList());
        return page;
    }

    @Override
    public PageInfo<OtherDeductionRecordDTO> recordListPage(OtherDeductionQueryParam queryParam) {
        //申报月份
        List<String> declareMonth = queryParam.getDeclareMonth();
        if (CollectionUtils.isNotEmpty(declareMonth)) {
            queryParam.setDeclareMonth(declareMonth.stream().map(e -> e + "-01 00:00:00").collect(Collectors.toList()));
            queryParam.setDeclareMonthDate(declareMonth.stream().map(e -> e + "-01 00:00:00").map(SalaryDateUtil::dateStrToLocalTime).collect(Collectors.toList()));
        }

        List<OtherDeductionRecordDTO> list = getOtherDeductionMapper().recordList(queryParam);
        PageInfo<OtherDeductionRecordDTO> page = SalaryPageUtil.buildPage(queryParam.getCurrent(), queryParam.getPageSize(),
                list, OtherDeductionRecordDTO.class);
        encryptUtil.decryptList(page.getList(), OtherDeductionRecordDTO.class);
        SalaryI18nUtil.i18nList(page.getList());
        return page;
    }


    @Override
    public Map<String, Object> preview(OtherDeductionImportParam importParam) {
        Map<String, Object> apidatas = new HashMap<String, Object>();

        //excel文件id
        String imageId = Util.null2String(importParam.getImageId());
        Validate.notBlank(imageId, "imageId为空");

        InputStream fileInputStream = null;
        try {
            fileInputStream = ImageFileManager.getInputStreamById(Integer.parseInt(imageId));
            Sheet sheet = ExcelSupport.parseFile(fileInputStream, 0, EXCEL_TYPE_XLSX);
            apidatas.put("headers", ExcelSupport.getSheetHeader(sheet, 0));
            apidatas.put("list", ExcelParseHelper.parse2List(sheet, 1, 0));
            return apidatas;
        } finally {
            IOUtils.closeQuietly(fileInputStream);
        }
    }


    public Map<String, Object> importData(OtherDeductionImportParam importParam) {

        Map<String, Object> apidatas = new HashMap<String, Object>();
        OtherDeductionBiz OtherDeductionBiz = new OtherDeductionBiz();

        //查询对于人员信息导入筛选的全局配置
        SalarySysConfPO salarySysConfPO = getSalarySysConfMapper().getOneByCode("matchEmployeeMode");
        String confValue = (salarySysConfPO != null && salarySysConfPO.getConfValue() != null && !"".equals(salarySysConfPO.getConfValue())) ? salarySysConfPO.getConfValue() : "0";

        //检验参数
        checkImportParam(importParam);

        //excel文件id
        String imageId = Util.null2String(importParam.getImageId());
        Validate.notBlank(imageId, "imageId为空");
        //税款所属期
        String declareMonthStr = Util.null2String(importParam.getDeclareMonth());
        //个税扣缴义务人
        String taxAgentId = Util.null2String(importParam.getTaxAgentId());

        InputStream fileInputStream = null;
        try {
            fileInputStream = ImageFileManager.getInputStreamById(Integer.parseInt(imageId));
            List<OtherDeductionListDTO> OtherDeductions = ExcelParseHelper.parse2Map(fileInputStream, OtherDeductionListDTO.class, 0, 1, 12, "OtherDeductionTemplate.xlsx");

            int total = OtherDeductions.size();
            int index = 0;
            int successCount = 0;
            int errorCount = 0;

            //人员信息
            List<DataCollectionEmployee> employees = getSalaryEmployeeService(user).listEmployee();
            // 获取所有个税扣缴义务人
            TaxAgentQueryParam param = TaxAgentQueryParam.builder().build();
            param.setFilterType(AuthFilterTypeEnum.ADMIN_DATA);
            Collection<TaxAgentPO> taxAgentList = getTaxAgentService(user).listAuth(param);
            //税款所属期
            Date declareMonth = SalaryDateUtil.stringToDate(declareMonthStr + "-01");
            // 获取已经核算的数据
            List<SalaryAcctEmployeePO> salaryAcctEmployees = getAddUpDeductionService(user).getAccountedEmployeeDataByTaxYearMonth(declareMonthStr);
            // 查询已有数据
            List<OtherDeductionPO> list = getOtherDeductionMapper().listSome(OtherDeductionPO.builder().declareMonth(declareMonth).build());

            // 错误excel内容
            List<Map> errorData = new ArrayList<>();
            //合规数据
            List<OtherDeductionPO> eligibleData = new ArrayList<>();

            for (int i = 0; i < OtherDeductions.size(); i++) {
                OtherDeductionListDTO dto = OtherDeductions.get(i);

                Date now = new Date();
                //待插入数据库对象
                OtherDeductionPO po = OtherDeductionPO.builder()
                        .tenantKey(DEFAULT_TENANT_KEY)
                        .createTime(now)
                        .updateTime(now)
                        .creator((long) user.getUID())
                        .declareMonth(declareMonth).build();

                //异常点数量
                int errorSum = 0;

                //行号
                String rowIndex = String.format("第%s行", i + 2);

                //相同的姓名
                String userName = dto.getUsername();
                String deparmentName = dto.getDepartmentName();
                String mobile = dto.getMobile();
                String workcode = dto.getJobNum();
                String idNo = dto.getIdNo();
                List<Long> employeeSameIds = new ArrayList<>();

                //筛选导入人员信息可以在人力资源池中匹配到的人员信息
                List<DataCollectionEmployee> emps = getSalaryEmployeeService(user).matchImportEmployee(confValue, employees, userName, deparmentName, mobile, workcode, idNo, null);
                //含在职和离职，选在职数据
                if (CollectionUtils.isNotEmpty(emps) && emps.size() > 1) {
                    employeeSameIds = emps.stream()
                            .filter(e -> UserStatusEnum.getNormalStatus().contains(e.getStatus()))
                            .map(DataCollectionEmployee::getEmployeeId)
                            .collect(Collectors.toList());
                }
                if (CollectionUtils.isNotEmpty(emps) && emps.size() == 1) {
                    employeeSameIds = emps.stream()
                            .map(DataCollectionEmployee::getEmployeeId)
                            .collect(Collectors.toList());
                }

                //当人员信息导入筛选的全局配置为"0"时，姓名才是必填项
                if (StringUtils.isBlank(userName) && "0".equals(confValue)) {
                    //姓名 不能为空
                    //错误消息对象
                    Map<String, String> errorMessageMap = Maps.newHashMap();
                    errorMessageMap.put("message", rowIndex + "姓名不能为空");
                    errorData.add(errorMessageMap);
                    errorSum += 1;
                } else if (CollectionUtils.isEmpty(employeeSameIds) || employeeSameIds.size() > 1) {
                    Map<String, String> errorMessageMap = Maps.newHashMap();
                    errorMessageMap.put("message", rowIndex + "员工信息不存在或者存在多个员工");
                    errorData.add(errorMessageMap);
                    errorSum += 1;
                } else {
                    Long employeeId = CollectionUtils.isNotEmpty(employeeSameIds) && employeeSameIds.size() == 1 ? employeeSameIds.get(0) : null;
                    if (employeeId != null && employeeId > 0) {
                        po.setEmployeeId(employeeId);
                    } else {
                        //姓名错误，系统内不存在该姓名
                        Map<String, String> errorMessageMap = Maps.newHashMap();
                        errorMessageMap.put("message", rowIndex + "姓名错误，系统内不存在该姓名");
                        errorData.add(errorMessageMap);
                        errorSum += 1;
                    }
                }


                String taxAgentName = dto.getTaxAgentName();
                if (StringUtils.isBlank(taxAgentName)) {
                    //个税扣缴义务人不能为空
                    Map<String, String> errorMessageMap = Maps.newHashMap();
                    errorMessageMap.put("message", rowIndex + "个税扣缴义务人不能为空");
                    errorData.add(errorMessageMap);
                    errorSum += 1;
                } else {
                    Optional<TaxAgentPO> optionalTemp = taxAgentList.stream().filter(m -> m.getName().equals(taxAgentName)).findFirst();
                    if (optionalTemp.isPresent()) {
                        if (StringUtils.isNotEmpty(taxAgentId) && !optionalTemp.get().getId().equals(Long.valueOf(taxAgentId))) {
                            //个税扣缴义务人与导入时选择的不一致
                            Map<String, String> errorMessageMap = Maps.newHashMap();
                            errorMessageMap.put("message", rowIndex + "个税扣缴义务人与导入时选择的不一致");
                            errorData.add(errorMessageMap);
                            errorSum += 1;
                        } else {
                            po.setTaxAgentId(optionalTemp.get().getId());
                        }
                    } else {
                        //个税扣缴义务人不存在
                        Map<String, String> errorMessageMap = Maps.newHashMap();
                        errorMessageMap.put("message", rowIndex + "个税扣缴义务人不存在或不在权限范围内");
                        errorData.add(errorMessageMap);
                        errorSum += 1;
                    }
                }

                //商业健康保险
                String businessHealthyInsurance = dto.getBusinessHealthyInsurance();
                po.setBusinessHealthyInsurance(businessHealthyInsurance);
                //税延养老保险
                String taxDelayEndowmentInsurance = dto.getTaxDelayEndowmentInsurance();
                po.setTaxDelayEndowmentInsurance(taxDelayEndowmentInsurance);
                //其他
                String otherDeduction = dto.getOtherDeduction();
                po.setOtherDeduction(otherDeduction);
                //准予扣除的捐赠额
                String deductionAllowedDonation = dto.getDeductionAllowedDonation();
                po.setDeductionAllowedDonation(deductionAllowedDonation);
                //个人养老金
                String privatePension = dto.getPrivatePension();
                po.setPrivatePension(privatePension);


                // 判断是否有核算过
                if (CollectionUtils.isNotEmpty(salaryAcctEmployees)) {
                    OtherDeductionPO finalPo = po;
                    Optional<SalaryAcctEmployeePO> optionalAcctEmp = salaryAcctEmployees.stream().filter(f -> f.getEmployeeId().equals(finalPo.getEmployeeId()) && f.getTaxAgentId().equals(finalPo.getTaxAgentId())).findFirst();
                    boolean isExist = list.stream().anyMatch(f -> f.getEmployeeId().equals(finalPo.getEmployeeId()) && f.getTaxAgentId().equals(finalPo.getTaxAgentId()));
                    if (optionalAcctEmp.isPresent() && isExist) {
                        Map<String, String> errorMessageMap = Maps.newHashMap();
                        errorMessageMap.put("message", rowIndex + "该年月这条数据已经核算过，不可导入");
                        errorData.add(errorMessageMap);
                        errorSum += 1;
                    }
                }


                if (errorSum == 0) {
                    successCount += 1;
                    // 合格数据
                    eligibleData.add(po);
                } else {
                    errorCount += 1;
                    // 添加错误数据
                }
            }

            //入库
            OtherDeductionBiz.handleImportData(eligibleData, user);

            apidatas.put("successCount", successCount);
            apidatas.put("errorCount", errorCount);
            apidatas.put("errorData", errorData);

        } finally {
            IOUtils.closeQuietly(fileInputStream);
        }
        return apidatas;
    }

    /**
     * 处理导入数据
     *
     * @param pos
     */
    public void handleImportData(List<OtherDeductionPO> pos) {
        if (CollectionUtils.isEmpty(pos)) {
            return;
        }
        OtherDeductionBiz otherDeductionBiz = new OtherDeductionBiz();
        OtherDeductionPO po = pos.get(0);
        // 多条相同人的则以第一条为准，如果逆序排列（用于重复的则以最后一条为准）Collections.reverse(pos);
        // 去重（通过记录的唯一条件(申报月份，人员id，个税扣缴义务人id)拼接）
        List<OtherDeductionPO> finalPos = pos.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(f -> f.getEmployeeId() + "-" + f.getTaxAgentId()))), ArrayList::new));
        // 查询已有数据
        List<OtherDeductionPO> list = otherDeductionBiz.listSome(OtherDeductionPO.builder().declareMonth(po.getDeclareMonth()).build());
        // 待修改的 本地已存在则更新【交集】
        List<OtherDeductionPO> updateList = list.stream().map(m -> {
            Optional<OtherDeductionPO> optional = finalPos.stream().filter(p -> (p.getEmployeeId() + "-" + p.getTaxAgentId()).equals(m.getEmployeeId() + "-" + m.getTaxAgentId())).findFirst();
            OtherDeductionPO temp = null;
            if (optional.isPresent()) {
                temp = optional.get();
                // 换成本地库的id
                temp.setId(m.getId());
            }
            return temp;
        }).filter(Objects::nonNull).collect(Collectors.toList());
        // 待新增的 导入比本地多，则新增【差集(导入 - local)】
        List<OtherDeductionPO> saveList = finalPos.stream().map(m -> {
            Optional<OtherDeductionPO> optional = list.stream().filter(p -> (p.getEmployeeId() + "-" + p.getTaxAgentId()).equals(m.getEmployeeId() + "-" + m.getTaxAgentId())).findFirst();
            OtherDeductionPO temp = null;
            if (!optional.isPresent()) {
                temp = m;
            }
            return temp;
        }).filter(Objects::nonNull).collect(Collectors.toList());

        // 修改
        if (CollectionUtils.isNotEmpty(updateList)) {
            otherDeductionBiz.batchUpdate(updateList);
        }
        // 保存
        if (CollectionUtils.isNotEmpty(saveList)) {
            otherDeductionBiz.batchSave(saveList);
        }
        // 记录日志
        // 根据月份、人员id查出保存的数据
        List<Long> empIds = saveList.stream().map(OtherDeductionPO::getEmployeeId).collect(Collectors.toList());
        List<OtherDeductionPO> insertList = otherDeductionBiz.listSome(OtherDeductionPO.builder().declareMonth(po.getDeclareMonth()).employeeIds(empIds).build());
        Map<String, OtherDeductionPO> insertMap = SalaryEntityUtil.convert2Map(insertList, p -> p.getTaxAgentId() + "-" + SalaryDateUtil.getFormatYearMonth(p.getDeclareMonth()) + "-" + p.getEmployeeId());
        saveList.forEach(save -> {
            OtherDeductionPO otherDeductionPO = insertMap.get(save.getTaxAgentId() + "-" + SalaryDateUtil.getFormatYearMonth(save.getDeclareMonth()) + "-" + save.getEmployeeId());
            if (otherDeductionPO != null) {
                updateList.add(otherDeductionPO);
            }
        });

        if (CollectionUtils.isNotEmpty(updateList)) {
            LoggerContext loggerContext = new LoggerContext<>();
            loggerContext.setUser(user);
            if (updateList.size() == 1) {
                loggerContext.setTargetId(updateList.get(0).getId().toString());
            }
            loggerContext.setTargetName(SalaryI18nUtil.getI18nLabel(0, "其他免税扣除"));
            loggerContext.setOperateType(OperateTypeEnum.ADD.getValue());
            loggerContext.setOperateTypeName(SalaryI18nUtil.getI18nLabel(0, "新增"));
            loggerContext.setOperatedesc(SalaryI18nUtil.getI18nLabel(0, "新增其他免税扣除"));
            loggerContext.setNewValueList(updateList);
            SalaryElogConfig.otherDeductionLoggerTemplate.write(loggerContext);
        }
    }

    private void checkImportParam(OtherDeductionImportParam importParam) {
        //excel文件id
        String imageId = Util.null2String(importParam.getImageId());
        //税款所属期
        String declareMonthStr = Util.null2String(importParam.getDeclareMonth());
        //个税扣缴义务人
        String taxAgentId = Util.null2String(importParam.getTaxAgentId());

        if (StringUtils.isBlank(imageId)) {
            throw new SalaryRunTimeException("文件不存在");
        }
        if (StringUtils.isBlank(declareMonthStr)) {
            throw new SalaryRunTimeException("税款所属期为空");
        }
    }


    /**
     * 导出
     *
     * @param param
     * @return
     */
    public XSSFWorkbook export(OtherDeductionQueryParam param) {

        //获取操作按钮资源
        List<List<Object>> rowList = getExcelRowList(param, true);

        // 记录日志
        String name = SalaryI18nUtil.getI18nLabel(0, "导出");
        LoggerContext<OtherDeductionPO> loggerContext = new LoggerContext<>();
        loggerContext.setTargetName(SalaryI18nUtil.getI18nLabel(0, "其他免税扣除"));
        loggerContext.setOperateType(OperateTypeEnum.EXCEL_EXPORT.getValue());
        loggerContext.setOperateTypeName(name);
        loggerContext.setOperatedesc(SalaryI18nUtil.getI18nLabel(0, "其他免税扣除") + "-" + name);
        loggerContext.setUser(user);
        SalaryElogConfig.otherDeductionLoggerTemplate.write(loggerContext);

        //获取excel
        return ExcelUtil.genWorkbookV2WithDefaultPattern(rowList, "其他免税扣除", 2);
    }


    /**
     * 获取excel数据行
     *
     * @return 导出数据行集合
     */
    private List<List<Object>> getExcelRowList(OtherDeductionQueryParam param, boolean hasData) {
        //excel标题
        List<Object> title = Arrays.asList("姓名", "个税扣缴义务人", "部门", "手机号", "工号", "证件号码", "入职日期", "商业健康保险", "税延养老保险", "其他", "准予扣除的捐赠额", "个人养老金");
        List<List<Object>> rowList = new ArrayList<>();
        rowList.add(title);

        if (hasData) {
            //排序配置
            OrderRuleVO orderRule = getSalarySysConfService(user).orderRule();
            param.setOrderRule(orderRule);

            param.setFilterType( AuthFilterTypeEnum.QUERY_DATA);
            List<OtherDeductionListDTO> list = listAuth(param);
            encryptUtil.decryptList(list, OtherDeductionListDTO.class);
            SalaryI18nUtil.i18nList(list);

            final List<List<Object>> dataRowList = Optional.ofNullable(list)
                    .map(List::stream)
                    .map(operatorStream -> operatorStream.map(dto -> {
                        List<Object> cellList = new ArrayList<>();
                        cellList.add(Util.null2String(dto.getUsername()));
                        cellList.add(Util.null2String(dto.getTaxAgentName()));
                        cellList.add(Util.null2String(dto.getDepartmentName()));
                        cellList.add(Util.null2String(dto.getMobile()));
                        cellList.add(Util.null2String(dto.getJobNum()));
                        cellList.add(Util.null2String(dto.getIdNo()));
                        cellList.add(Util.null2String(dto.getHiredate()));
                        cellList.add(NumberUtil.isNumber(dto.getBusinessHealthyInsurance()) ? new BigDecimal(dto.getBusinessHealthyInsurance()) : Util.null2String(dto.getBusinessHealthyInsurance()));
                        cellList.add(NumberUtil.isNumber(dto.getTaxDelayEndowmentInsurance()) ? new BigDecimal(dto.getTaxDelayEndowmentInsurance()) : Util.null2String(dto.getTaxDelayEndowmentInsurance()));
                        cellList.add(NumberUtil.isNumber(dto.getOtherDeduction()) ? new BigDecimal(dto.getOtherDeduction()) : Util.null2String(dto.getOtherDeduction()));
                        cellList.add(NumberUtil.isNumber(dto.getDeductionAllowedDonation()) ? new BigDecimal(dto.getDeductionAllowedDonation()) : Util.null2String(dto.getDeductionAllowedDonation()));
                        cellList.add(NumberUtil.isNumber(dto.getPrivatePension()) ? new BigDecimal(dto.getPrivatePension()) : Util.null2String(dto.getPrivatePension()));
                        return cellList;
                    }).collect(Collectors.toList()))
                    .orElse(Collections.emptyList());

            rowList.addAll(dataRowList);
        }

        return rowList;
    }


    /**
     * 导出详情列表
     *
     * @param param
     * @return
     */
    public XSSFWorkbook exportDetail(OtherDeductionQueryParam param) {

        OtherDeductionBiz biz = new OtherDeductionBiz();

        Long id = param.getOtherTaxExemptDeductionId();
        if (id == null) {
            throw new SalaryRunTimeException("id不能为空");
        }

        OtherDeductionPO po = biz.getById(id);
        if (po == null) {
            throw new SalaryRunTimeException(String.format("其他免税扣除不存在" + "[id:%s]", id));
        }

        List<DataCollectionEmployee> employeeList = getSalaryEmployeeService(user).getEmployeeByIds(Collections.singletonList(po.getEmployeeId()));
        if (CollectionUtils.isEmpty(employeeList)) {
            throw new SalaryRunTimeException("员工信息不存在");
        }

        //构建参数
        param.setEmployeeId(po.getEmployeeId());
        //申报月份
        List<String> declareMonth = param.getDeclareMonth();
        if (CollectionUtils.isNotEmpty(declareMonth)) {
            param.setDeclareMonth(declareMonth.stream().map(e -> e + "-01 00:00:00").collect(Collectors.toList()));
            param.setDeclareMonthDate(declareMonth.stream().map(e -> e + "-01 00:00:00").map(SalaryDateUtil::dateStrToLocalTime).collect(Collectors.toList()));
        }


        //获取操作按钮资源
        List<List<Object>> rowList = getExcelRowDetailList(param);

        //获取excel
        return ExcelUtil.genWorkbookWithDefaultPattern(rowList, "其他免税扣除明细", 2);
    }


    /**
     * 导出详情
     *
     * @param param
     * @return
     */
    private List<List<Object>> getExcelRowDetailList(OtherDeductionQueryParam param) {
        //excel标题
        List<Object> title = Arrays.asList("姓名", "申报月份", "个税扣缴义务人", "部门", "手机号", "工号", "商业健康保险", "税延养老保险", "其他", "准予扣除的捐赠额", "个人养老金");

        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM");
        //查询详细信息
        List<OtherDeductionRecordDTO> list = getOtherDeductionMapper().recordList(param);
        encryptUtil.decryptList(list, OtherDeductionRecordDTO.class);
        SalaryI18nUtil.i18nList(list);
        final List<List<Object>> dataRowList = Optional.ofNullable(list)
                .map(List::stream)
                .map(operatorStream -> operatorStream.map(dto -> {
                    List<Object> cellList = new ArrayList<>();
                    cellList.add(Util.null2String(dto.getUsername()));
                    cellList.add(Util.null2String(dto.getDeclareMonth() == null ? "" : formatter.format(dto.getDeclareMonth())));
                    cellList.add(Util.null2String(dto.getTaxAgentName()));
                    cellList.add(Util.null2String(dto.getDepartmentName()));
                    cellList.add(Util.null2String(dto.getMobile()));
                    cellList.add(Util.null2String(dto.getJobNum()));
                    cellList.add(NumberUtil.isNumber(dto.getBusinessHealthyInsurance()) ? new BigDecimal(dto.getBusinessHealthyInsurance()) : Util.null2String(dto.getBusinessHealthyInsurance()));
                    cellList.add(NumberUtil.isNumber(dto.getTaxDelayEndowmentInsurance()) ? new BigDecimal(dto.getTaxDelayEndowmentInsurance()) : Util.null2String(dto.getTaxDelayEndowmentInsurance()));
                    cellList.add(NumberUtil.isNumber(dto.getOtherDeduction()) ? new BigDecimal(dto.getOtherDeduction()) : Util.null2String(dto.getOtherDeduction()));
                    cellList.add(NumberUtil.isNumber(dto.getDeductionAllowedDonation()) ? new BigDecimal(dto.getDeductionAllowedDonation()) : Util.null2String(dto.getDeductionAllowedDonation()));
                    cellList.add(NumberUtil.isNumber(dto.getPrivatePension()) ? new BigDecimal(dto.getPrivatePension()) : Util.null2String(dto.getPrivatePension()));
                    return cellList;
                }).collect(Collectors.toList()))
                .orElse(Collections.emptyList());

        List<List<Object>> rowList = new ArrayList<>();
        rowList.add(title);
        rowList.addAll(dataRowList);
        return rowList;
    }


    @Override
    public List<OtherDeductionPO> getOtherDeductionList(YearMonth declareMonth, List<Long> employeeIds, List<Long> taxAgentIds) {
        if (declareMonth == null) {
            throw new SalaryRunTimeException(SalaryI18nUtil.getI18nLabel(100342, "参数有误：申报月份必传"));
        }
        OtherDeductionBiz OtherDeductionBiz = new OtherDeductionBiz();
        return OtherDeductionBiz.listSome(OtherDeductionPO.builder().declareMonth(SalaryDateUtil.toDateStartOfMonth(declareMonth)).employeeIds(employeeIds).taxAgentIds(taxAgentIds).build());
    }

    @Override
    public void editData(OtherDeductionParam otherDeductionParam) {
        String declareMonthStr = otherDeductionParam.getDeclareMonth();
        OtherDeductionBiz OtherDeductionBiz = new OtherDeductionBiz();

        // 获取所有个税扣缴义务人
        TaxAgentQueryParam param = TaxAgentQueryParam.builder().build();
        param.setFilterType(AuthFilterTypeEnum.ADMIN_DATA);
        Collection<TaxAgentPO> taxAgentList = getTaxAgentService(user).listAuth(param);
        OtherDeductionPO byId = OtherDeductionBiz.getById(otherDeductionParam.getId());
        if (byId == null) {
            throw new SalaryRunTimeException("该数据不存在！");
        }
        Long taxAgentId = byId.getTaxAgentId();
        boolean canEdit = taxAgentList.stream().anyMatch(t -> Objects.equals(t.getId(), taxAgentId));
        if (!canEdit) {
            //没有编辑权限
            throw new SalaryRunTimeException("该个税扣缴义务人无权限编辑此数据！");
        }
        // 获取已经核算的数据
        List<SalaryAcctEmployeePO> salaryAcctEmployees = getAddUpDeductionService(user).getAccountedEmployeeDataByTaxYearMonth(declareMonthStr);
        // 判断是否有核算过
        if (CollectionUtils.isNotEmpty(salaryAcctEmployees)) {
            Optional<SalaryAcctEmployeePO> optionalAcctEmp = salaryAcctEmployees.stream().filter(f -> f.getEmployeeId().equals(otherDeductionParam.getEmployeeId()) && f.getTaxAgentId().equals(otherDeductionParam.getTaxAgentId())).findFirst();
            if (optionalAcctEmp.isPresent()) {
                throw new SalaryRunTimeException("该年月这条数据已经核算过,不可进行编辑！");
            }
        }
        ArrayList<OtherDeductionPO> updateList = new ArrayList<>();
        OtherDeductionPO build = OtherDeductionPO.builder()
                .id(otherDeductionParam.getId())
                .businessHealthyInsurance(otherDeductionParam.getBusinessHealthyInsurance())
                .taxDelayEndowmentInsurance(otherDeductionParam.getTaxDelayEndowmentInsurance())
                .otherDeduction(otherDeductionParam.getOtherDeduction())
                .deductionAllowedDonation(otherDeductionParam.getDeductionAllowedDonation())
                .privatePension(otherDeductionParam.getPrivatePension())
                .updateTime(new Date())
                .build();
        updateList.add(build);
        OtherDeductionBiz.batchUpdate(updateList);

        // 记录日志
        OtherDeductionPO newValue = OtherDeductionBiz.getById(build.getId());
        String name = SalaryI18nUtil.getI18nLabel(0, "编辑");
        LoggerContext<AddUpDeduction> loggerContext = new LoggerContext<>();
        loggerContext.setTargetId(newValue.getId().toString());
        loggerContext.setTargetName(SalaryI18nUtil.getI18nLabel(0, "其他免税扣除") + "-" + newValue.getId());
        loggerContext.setOperateType(OperateTypeEnum.UPDATE.getValue());
        loggerContext.setOperateTypeName(name);
        loggerContext.setOperatedesc(SalaryI18nUtil.getI18nLabel(0, "其他免税扣除") + "-" + SalaryI18nUtil
                .getI18nLabel(0, "编辑"));
        loggerContext.setOldValues(byId);
        loggerContext.setNewValues(newValue);
        loggerContext.setUser(user);
        SalaryElogConfig.otherDeductionLoggerTemplate.write(loggerContext);
    }

    @Override
    public void createData(OtherDeductionParam otherDeductionParam) {
        long currentEmployeeId = user.getUID();
        //查询对于人员信息导入筛选的全局配置
        SalarySysConfPO salarySysConfPO = getSalarySysConfMapper().getOneByCode("matchEmployeeMode");
        String confValue = (salarySysConfPO != null && salarySysConfPO.getConfValue() != null && !"".equals(salarySysConfPO.getConfValue())) ? salarySysConfPO.getConfValue() : "0";

        //税款所属期
        String declareMonthStr = Util.null2String(otherDeductionParam.getDeclareMonth());
        //人员信息
        List<DataCollectionEmployee> employees = getSalaryEmployeeService(user).listEmployee();
        // 获取所有个税扣缴义务人
        TaxAgentQueryParam param = TaxAgentQueryParam.builder().build();
        param.setFilterType(AuthFilterTypeEnum.ADMIN_DATA);
        Collection<TaxAgentPO> taxAgentList = getTaxAgentService(user).listAuth(param);
        //税款所属期
        Date declareMonth = SalaryDateUtil.stringToDate(declareMonthStr + "-01");
        // 获取已经核算的数据
        List<SalaryAcctEmployeePO> salaryAcctEmployees = getAddUpDeductionService(user).getAccountedEmployeeDataByTaxYearMonth(declareMonthStr);
        // 查询已有数据
        List<OtherDeductionPO> list = getOtherDeductionMapper().listSome(OtherDeductionPO.builder().declareMonth(declareMonth).build());
        //合规数据
        List<OtherDeductionPO> insertData = new ArrayList<>();
        List<TaxAgentManageRangeEmployeeDTO.TaxAgentEmployee> taxAgentEmployees = Lists.newArrayList();
        Date now = new Date();
        //待插入数据库对象
        OtherDeductionPO po = OtherDeductionPO.builder()
                .tenantKey(DEFAULT_TENANT_KEY)
                .createTime(now)
                .updateTime(now)
                .creator((long) user.getUID())
                .declareMonth(declareMonth).build();

        //筛选导入人员信息可以在人力资源池中匹配到的人员信息
        boolean employeeSameId = employees.stream().anyMatch(e -> Objects.equals(e.getEmployeeId(), otherDeductionParam.getEmployeeId()));
        if (!employeeSameId) {
            throw new SalaryRunTimeException("员工信息不存在");
        }
        po.setEmployeeId(otherDeductionParam.getEmployeeId());
        String taxAgentName = otherDeductionParam.getTaxAgentName();
        if (StringUtils.isBlank(taxAgentName)) {
            //个税扣缴义务人不能为空
            throw new SalaryRunTimeException("个税扣缴义务人不能为空");
        } else {
            Optional<TaxAgentPO> optionalTemp = taxAgentList.stream().filter(m -> m.getName().equals(taxAgentName)).findFirst();
            if (optionalTemp.isPresent()) {
                po.setTaxAgentId(optionalTemp.get().getId());
            } else {
                //个税扣缴义务人不存在或不在权限范围内
                throw new SalaryRunTimeException("个税扣缴义务人不存在或不在权限范围内");
            }
        }

        //商业健康保险
        String businessHealthyInsurance = otherDeductionParam.getBusinessHealthyInsurance();
        po.setBusinessHealthyInsurance(businessHealthyInsurance);
        //税延养老保险
        String taxDelayEndowmentInsurance = otherDeductionParam.getTaxDelayEndowmentInsurance();
        po.setTaxDelayEndowmentInsurance(taxDelayEndowmentInsurance);
        //其他
        String otherDeduction = otherDeductionParam.getOtherDeduction();
        po.setOtherDeduction(otherDeduction);
        //准予扣除的捐赠额
        String deductionAllowedDonation = otherDeductionParam.getDeductionAllowedDonation();
        po.setDeductionAllowedDonation(deductionAllowedDonation);
        //个人养老金
        String privatePension = otherDeductionParam.getPrivatePension();
        po.setPrivatePension(privatePension);


        // 判断是否有核算过
        if (CollectionUtils.isNotEmpty(salaryAcctEmployees)) {
            OtherDeductionPO finalPo = po;
            Optional<SalaryAcctEmployeePO> optionalAcctEmp = salaryAcctEmployees.stream().filter(f -> f.getEmployeeId().equals(finalPo.getEmployeeId()) && f.getTaxAgentId().equals(finalPo.getTaxAgentId())).findFirst();
            boolean isExist = list.stream().anyMatch(f -> f.getEmployeeId().equals(finalPo.getEmployeeId()) && f.getTaxAgentId().equals(finalPo.getTaxAgentId()));
            if (optionalAcctEmp.isPresent() && isExist) {
                throw new SalaryRunTimeException("该年月这条数据已经核算过,不可导入");
            }
        }
        insertData.add(po);
        //入库
        handleImportData(insertData);
    }

    @Override
    public void deleteSelectData(AddUpDeductionRecordDeleteParam deleteParam) {
        // 获取所有个税扣缴义务人
        TaxAgentQueryParam param = TaxAgentQueryParam.builder().build();
        param.setFilterType(AuthFilterTypeEnum.ADMIN_DATA);
        Collection<TaxAgentPO> taxAgentList = getTaxAgentService(user).listAuth(param);
        OtherDeductionBiz otherDeductionBiz = new OtherDeductionBiz();
        String declareMonthStr = deleteParam.getDeclareMonth();
        List<Long> deleteIds = deleteParam.getIds();
        // 已经核算过的不可操作
        // 获取已经核算的数据
        List<SalaryAcctEmployeePO> salaryAcctEmployees = getAddUpDeductionService(user).getAccountedEmployeeDataByTaxYearMonth(declareMonthStr);
        // 判断是否有核算过
        List<Long> deleteList = new ArrayList<>();
        List<OtherDeductionPO> resultList = new ArrayList<>();
        for (int i = 0; i < deleteIds.size(); i++) {
            Long id = deleteIds.get(i);
            OtherDeductionPO byId = otherDeductionBiz.getById(id);
            if (byId == null) {
                throw new SalaryRunTimeException("数据不存在或已被删除！");
            }
            // 判断是否在个税扣缴义务人范围内
            Optional<TaxAgentPO> first = taxAgentList.stream().filter(m -> Objects.equals(m.getId(), byId.getTaxAgentId())).findFirst();
            if (!first.isPresent()) {
                throw new SalaryRunTimeException("个税扣缴义务人不存在或不在权限范围内");
            }
            // 判断用户是否存在
            if (CollectionUtils.isNotEmpty(salaryAcctEmployees)) {
                Optional<SalaryAcctEmployeePO> optionalAcctEmp = salaryAcctEmployees.stream().filter(f -> f.getEmployeeId().equals(byId.getEmployeeId()) && f.getTaxAgentId().equals(byId.getTaxAgentId())).findFirst();
                if (optionalAcctEmp.isPresent()) {
                    throw new SalaryRunTimeException("所选数据在该年月中已经核算过并归档,不可进行删除！");
                }
            }
            deleteList.add(byId.getId());
            resultList.add(byId);
        }
        otherDeductionBiz.batchDeleteByIDS(deleteList);
        // 记录日志
        resultList.stream().forEach(r -> {
            LoggerContext loggerContext = new LoggerContext<>();
            loggerContext.setTargetId(r.getId().toString());
            loggerContext.setTargetName(SalaryI18nUtil.getI18nLabel(0, "其他免税扣除") + "-" + r.getId());
            loggerContext.setOperateType(OperateTypeEnum.DELETE.getValue());
            loggerContext.setOperateTypeName(SalaryI18nUtil.getI18nLabel(0, "删除"));
            loggerContext.setOperatedesc(SalaryI18nUtil.getI18nLabel(0, "其他免税扣除") + "-" + SalaryI18nUtil
                    .getI18nLabel(0, "删除"));
            loggerContext.setOldValues(r);
            loggerContext.setUser(user);
            SalaryElogConfig.otherDeductionLoggerTemplate.write(loggerContext);
        });
    }

    @Override
    public void deleteAllData(AddUpDeductionRecordDeleteParam deleteParam) {
        String declareMonthStr = deleteParam.getDeclareMonth();
        long currentEmployeeId = user.getUID();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        // 获取所有个税扣缴义务人
        TaxAgentQueryParam param = TaxAgentQueryParam.builder().build();
        param.setFilterType(AuthFilterTypeEnum.ADMIN_DATA);
        Collection<TaxAgentPO> taxAgentList = getTaxAgentService(user).listAuth(param);
        List<Long> taxAgentIds = taxAgentList.stream().map(TaxAgentPO::getId).collect(Collectors.toList());
        OtherDeductionBiz otherDeductionBiz = new OtherDeductionBiz();
        Date declareMonthDate = null;
        try {
            declareMonthDate = sdf.parse(declareMonthStr + "-01");
        } catch (Exception e) {
            throw new SalaryRunTimeException("日期异常");
        }

        OtherDeductionPO queryParam = null;
        if (deleteParam.getTaxAgentId() != null && (!deleteParam.getTaxAgentId().equals(""))) {
            // 设置了个税扣缴义务人
            Long taxAgentId = SalaryEntityUtil.string2Long(deleteParam.getTaxAgentId());
            boolean canDelete = taxAgentIds.stream().anyMatch(t -> Objects.equals(t, taxAgentId));
            if (!canDelete) {
                throw new SalaryRunTimeException("个税扣缴义务人不存在或不在权限范围内!");
            }
            ArrayList<Long> tai = new ArrayList<>();
            tai.add(taxAgentId);
            queryParam = OtherDeductionPO.builder().declareMonth(declareMonthDate).taxAgentIds(tai).build();
        } else {
            queryParam = OtherDeductionPO.builder().declareMonth(declareMonthDate).taxAgentIds(taxAgentIds).build();
        }
        // 获取所有想要删除的数据
        List<OtherDeductionPO> list = otherDeductionBiz.listSome(queryParam);
        list = getAuthService(user).auth(list, AuthFilterTypeEnum.ADMIN_DATA, OtherDeductionPO.class);
        // 获取已经核算的数据
        List<SalaryAcctEmployeePO> salaryAcctEmployees = getAddUpDeductionService(user).getAccountedEmployeeDataByTaxYearMonth(declareMonthStr);
        for (OtherDeductionPO item : list) {
            if (CollectionUtils.isNotEmpty(salaryAcctEmployees)) {
                Optional<SalaryAcctEmployeePO> optionalAcctEmp = salaryAcctEmployees.stream().filter(f -> f.getEmployeeId().equals(item.getEmployeeId()) && f.getTaxAgentId().equals(item.getTaxAgentId())).findFirst();
                if (optionalAcctEmp.isPresent()) {
                    throw new SalaryRunTimeException("有员工在该年月中已经完成核算并归档，不能进行一键清空！");
                }
            }
        }
        List<Long> deleteIds = list.stream().map(OtherDeductionPO::getId).collect(Collectors.toList());
        otherDeductionBiz.batchDeleteByIDS(deleteIds);

        // 记录日志
        Collection<Long> finalTaxAgentIds = queryParam.getTaxAgentIds();
        List<String> taxAgentNames = taxAgentList.stream().filter(t -> finalTaxAgentIds.contains(t.getId()))
                .map(TaxAgentPO::getName).collect(Collectors.toList());
        String name = declareMonthStr + " " + StringUtils.join(taxAgentNames, ",");
        LoggerContext<OtherDeductionPO> loggerContext = new LoggerContext<>();
        loggerContext.setTargetName(name);
        loggerContext.setOperateType(OperateTypeEnum.CLEAR.getValue());
        loggerContext.setOperateTypeName(SalaryI18nUtil.getI18nLabel(0, "一键清空"));
        loggerContext.setOperatedesc(SalaryI18nUtil.getI18nLabel(0, "其他免税扣除") + "-" + SalaryI18nUtil
                .getI18nLabel(0, "一键清空：") + name);
        loggerContext.setUser(user);
        SalaryElogConfig.otherDeductionLoggerTemplate.write(loggerContext);
    }

    @Override
    public OtherDeductionRecordDTO getOtherDeduction(OtherDeductionParam otherDeductionParam) {
        ArrayList<Long> ids = new ArrayList<>();
        ids.add(otherDeductionParam.getId());
        OtherDeductionQueryParam build = OtherDeductionQueryParam.builder().ids(ids).build();
        List<OtherDeductionRecordDTO> list = getOtherDeductionMapper().recordList(build);
        encryptUtil.decryptList(list, OtherDeductionRecordDTO.class);

        if (list == null || list.size() == 0) {
            throw new SalaryRunTimeException("该数据不存在！");
        }
        return list.get(0);
    }

    @Override
    public String extendToLastMonth(OtherDeductionExtendLastParam param) {
        //查询已经核算的数据
        List<SalaryAcctEmployeePO> salaryAcctEmployees = getAddUpDeductionService(user).getAccountedEmployeeDataByTaxYearMonth(param.getDeclareMonth());
        Map<String, List<SalaryAcctEmployeePO>> acctInfoMap = salaryAcctEmployees.stream()
                .distinct().collect(Collectors.groupingBy(
                        i -> i.getTaxAgentId() + "" + i.getEmployeeId()));

        // 查找到所有个税扣缴义务人
        TaxAgentQueryParam taxAgentQueryParam = TaxAgentQueryParam.builder().build();
        taxAgentQueryParam.setFilterType(AuthFilterTypeEnum.ADMIN_DATA);
        Collection<TaxAgentPO> taxAgentPOS = getTaxAgentService(user).listAuth(taxAgentQueryParam);
        List<Long> taxAgentIds = taxAgentPOS.stream().map(TaxAgentPO::getId).collect(Collectors.toList());

        //查询上月数据
        LocalDateTime lastMonth = param.getYearMonthTime().minusMonths(1);
        OtherDeductionQueryParam queryParam = OtherDeductionQueryParam.builder()
                .taxAgentIds(taxAgentIds)
                .declareMonthDate(Collections.singletonList(
                        SalaryDateUtil.toDate(lastMonth))
                ).build();
        List<OtherDeductionListDTO> lastMonthInfo = getOtherDeductionMapper().list(queryParam);

        // 查询当前月id
        queryParam.setDeclareMonthDate(Collections.singletonList(
                SalaryDateUtil.toDate(param.getYearMonthTime())
        ));
        Map<String, Long> updateIdMap = getOtherDeductionMapper()
                .list(queryParam).stream()
                .collect(Collectors.toMap(i -> i.getEmployeeId() + "" + i.getTaxAgentId(), OtherDeductionListDTO::getId));

        List<OtherDeductionPO> insertInfo = new ArrayList<>();
        List<OtherDeductionPO> updatetInfo = new ArrayList<>();
        for (OtherDeductionListDTO dto : lastMonthInfo) {
            OtherDeductionPO po
                    = JSONObject.parseObject(JSONObject.toJSONString(dto), OtherDeductionPO.class);
            po.setDeclareMonth(SalaryDateUtil.toDate(param.getYearMonthTime()));
            po.setUpdateTime(new Date());
            po.setId(null);
            po.setTenantKey(SalaryDefaultTenantConstant.DEFAULT_TENANT_KEY);
            boolean hasOld = updateIdMap.containsKey(po.getEmployeeId() + "" + po.getTaxAgentId());
            if (hasOld && !acctInfoMap.containsKey(po.getTaxAgentId() + "" + po.getEmployeeId())) {
                //核算过的数据直接跳过不做更改
                po.setId(updateIdMap.get(po.getEmployeeId() + "" + po.getTaxAgentId()));
                updatetInfo.add(po);
            } else if (!hasOld) {
                po.setCreator((long) user.getUID());
                po.setCreateTime(new Date());
                insertInfo.add(po);
            }
        }

        getOtherDeductionBiz().batchSave(insertInfo);
        getOtherDeductionBiz().batchUpdate(updatetInfo);

        //记录日志
        // 根据月份、人员id查出保存的数据
        List<Long> empIds = insertInfo.stream().map(OtherDeductionPO::getEmployeeId).collect(Collectors.toList());
        List<OtherDeductionPO> insertList = getOtherDeductionBiz().listSome(OtherDeductionPO.builder().declareMonth(SalaryDateUtil.stringToDate(param.getDeclareMonth())).employeeIds(empIds).build());
        Map<String, OtherDeductionPO> insertMap = SalaryEntityUtil.convert2Map(insertList, p -> p.getTaxAgentId() + "-" + SalaryDateUtil.getFormatYearMonth(p.getDeclareMonth()) + "-" + p.getEmployeeId());
        insertList.forEach(save -> {
            OtherDeductionPO otherDeductionPO = insertMap.get(save.getTaxAgentId() + "-" + SalaryDateUtil.getFormatYearMonth(save.getDeclareMonth()) + "-" + save.getEmployeeId());
            if (otherDeductionPO != null) {
                updatetInfo.add(otherDeductionPO);
            }
        });

        if (CollectionUtils.isNotEmpty(updatetInfo)) {
            String yearMonthStr = SalaryDateUtil.getFormatYearMonth(lastMonth.toLocalDate());
            LoggerContext loggerContext = new LoggerContext();
            loggerContext.setUser(user);
            loggerContext.setTargetName(SalaryI18nUtil.getI18nLabel(0, "沿用上月 " + yearMonthStr));
            loggerContext.setOperateType(OperateTypeEnum.ADD.getValue());
            loggerContext.setOperateTypeName(SalaryI18nUtil.getI18nLabel(0, "沿用上月其他免税扣除"));
            loggerContext.setOperatedesc(SalaryI18nUtil.getI18nLabel(0, "沿用上月 " + yearMonthStr + " 其他免税扣除"));
            loggerContext.setNewValueList(updatetInfo);
            SalaryElogConfig.otherDeductionLoggerTemplate.write(loggerContext);
        }

        return "";
    }

    @Override
    public List<OtherDeductionListDTO> listAuth(OtherDeductionQueryParam queryParam) {
        List<OtherDeductionListDTO> list = getOtherDeductionMapper().list(queryParam);
        list = getAuthService(user).auth(list, queryParam.getFilterType(), OtherDeductionListDTO.class);
        return list;
    }

    @Override
    public XSSFWorkbook downloadTemplate(OtherDeductionQueryParam param) {
        // 1.工作簿名称
        String sheetName = SalaryI18nUtil.getI18nLabel(101604, "其他免税扣除导入模板");
        //获取操作按钮资源
        List<List<Object>> rowList = getExcelRowList(param, param.isHasData());

        // 4.注释
        List<ExcelComment> excelComments = Lists.newArrayList();
        excelComments.add(new ExcelComment(0, 0, 3, 2, SalaryI18nUtil.getI18nLabel(100344, "必填")));
        excelComments.add(new ExcelComment(1, 0, 4, 2, SalaryI18nUtil.getI18nLabel(100344, "必填")));
        excelComments.add(new ExcelComment(7, 0, 10, 2, SalaryI18nUtil.getI18nLabel(100344, "输入数字")));
        excelComments.add(new ExcelComment(8, 0, 11, 2, SalaryI18nUtil.getI18nLabel(100344, "输入数字")));
        excelComments.add(new ExcelComment(9, 0, 12, 2, SalaryI18nUtil.getI18nLabel(100344, "输入数字")));
        excelComments.add(new ExcelComment(10, 0, 13, 2, SalaryI18nUtil.getI18nLabel(100344, "输入数字")));
        excelComments.add(new ExcelComment(11, 0, 14, 2, SalaryI18nUtil.getI18nLabel(100344, "输入数字")));

        XSSFWorkbook book = ExcelUtil.genWorkbookV2(rowList, sheetName, excelComments);
        return book;
    }

}
